﻿// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------
namespace SRTSolutions.Elevate
{
    using global::System;
    using global::System.Collections.Generic;

    /// <summary>
    /// Contains methods for generating sequences (IEnumerables)
    /// </summary>
    public static partial class Seq
    {

        private class EnumeratorFromDisposable<T, TInput> : IEnumerator<T> where TInput :IDisposable
        {
            IEnumerator<T> enumerator;
            TInput input;

            public EnumeratorFromDisposable(Func<TInput> creator, Func<TInput, IEnumerable<T>> generator)
            {
                this.input = creator();

                try
                {
                    var enumerable = generator(input);
                    this.enumerator = enumerable.GetEnumerator();
                }
                catch(Exception)
                {
                    this.input.Dispose();
                    throw;
                }
            }

            #region IEnumerator<T> Members

            public T Current
            {
                get { return enumerator.Current; }
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                this.input.Dispose();
                this.enumerator.Dispose();
            }

            #endregion

            #region IEnumerator Members

            object System.Collections.IEnumerator.Current
            {
                get { return this.enumerator.Current; }
            }

            public bool MoveNext()
            {
                return this.enumerator.MoveNext();
            }

            public void Reset()
            {
                this.enumerator.Reset();
            }

            #endregion
        }

        private class EnumerableFromDisposable<T, TInput> : IEnumerable<T> where TInput :IDisposable
        {
            Func<TInput> creator;
            Func<TInput, IEnumerable<T>> generator;

            public EnumerableFromDisposable(Func<TInput> creator, Func<TInput, IEnumerable<T>> generator)
            {
                this.creator = creator;
                this.generator = generator;
            }

            #region IEnumerable<T> Members

            public IEnumerator<T> GetEnumerator()
            {
                return new EnumeratorFromDisposable<T, TInput>(creator, generator);
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #endregion
        }

        /// <summary>
        /// Creates a sequence from a function that returns an IDisposable, and a 
        /// generator function that takes the IDisposable and returns a sequence.
        /// 
        /// Calls the IDisposable creating function when an enumerator is requested
        /// from the return sequence, and disposes of the IDisposable when the
        /// enumerator is disposed.
        /// </summary>
        /// <typeparam name="T">The type of the output sequence</typeparam>
        /// <typeparam name="TInput">The type of the IDisposable.</typeparam>
        /// <param name="getInput">The IDisposable creating function.</param>
        /// <param name="generator">The sequence generator.</param>
        /// <returns></returns>
        public static IEnumerable<T> CreateFromDisposable<T, TInput>(Func<TInput> getInput,
                                                                     Func<TInput, IEnumerable<T>> generator)
            where TInput : IDisposable
        {
            if (getInput == null)
                throw new ArgumentNullException("getInput", "getInput is null.");
            if (generator == null)
                throw new ArgumentNullException("generator", "generator is null.");

            return new EnumerableFromDisposable<T, TInput>(getInput, generator);
        }

        /// <summary>
        /// Takes a sequence generating function and returns the sequence that it generates.
        /// The function is only executed when an enumerator for the sequence is requested.
        /// 
        /// Note: The generator function is executed every time an enumerator is requested.
        /// </summary>
        /// <typeparam name="T">The type of each item in the sequence.</typeparam>
        /// <param name="generator">The generator function.</param>
        /// <returns></returns>
        public static IEnumerable<T> Delay<T>(Func<IEnumerable<T>> generator)
        {
            if (generator == null)
                throw new ArgumentNullException("generator", "generator is null.");

            return DelayHelper(generator);
        }

        private static IEnumerable<T> DelayHelper<T>(Func<IEnumerable<T>> generator)
        {
            foreach (var item in generator())
            {
                yield return item;
            }
        }

        /// <summary>
        /// Creates a sequence by executing the generator function a specified number of times.
        /// Each value is computed as needed, and the number of times that the generator has
        /// been called is passed as an argument each time the generator is called.
        /// </summary>
        /// <typeparam name="TResult">The type of the items in the result sequence.</typeparam>
        /// <param name="count">The number of times to execute the generator.</param>
        /// <param name="generator">The generator.</param>
        /// <returns></returns>
        public static IEnumerable<TResult> Times<TResult>(this int count, Func<int, TResult> generator)
        {
            if (count < 0)
                throw new ArgumentOutOfRangeException("count", "count must be greater than or equal to 0.");
            if (generator == null)
                throw new ArgumentNullException("generator", "generator is null.");

            return TimesHelper(count, generator);
        }

        /// <summary>
        /// Creates a sequence by executing the generator function a specified number of times.
        /// Each value is computed as needed.
        /// </summary>
        /// <typeparam name="TResult">The type of the items in the result sequence.</typeparam>
        /// <param name="count">The number of times to execute the generator.</param>
        /// <param name="generator">The generator.</param>
        /// <returns></returns>
        public static IEnumerable<TResult> Times<TResult>(this int count, Func<TResult> generator)
        {
            return count.Times(ignored => generator());
        }

        private static IEnumerable<TResult> TimesHelper<TResult>(this int count, Func<int, TResult> generator)
        {
            for (int i = 0; i < count; i++)
            {
                yield return generator(i);
            }
        }

        /// <summary>
        /// Creates a sequence by executing the generator function an infinite number of times.
        /// Each value is computed as needed, and the number of times that the generator has
        /// been called is passed as an argument each time the generator is called.
        /// </summary>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="generator">The generator.</param>
        /// <returns></returns>
        public static IEnumerable<TResult> InfiniteTimes<TResult>(Func<int, TResult> generator)
        {
            if (generator == null)
                throw new ArgumentNullException("generator", "generator is null.");

            return TimesHelper(int.MaxValue, generator);
        }

        /// <summary>
        /// Generates a sequence using the specified generator function. Maintains a state keeping variable
        /// from one application of the generator to the next.
        /// </summary>
        /// <typeparam name="T">The type of the state variable.</typeparam>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="start">The start state.</param>
        /// <param name="generator">The generator function.</param>
        /// <returns></returns>
#if NET_3_5
        public static IEnumerable<TResult> Unfold<T, TResult>(T start, Func<T, Option<Tuple<TResult, T>>> generator)
#else
        public static IEnumerable<TResult> Unfold<T, TResult>(T start, Func<T, Option<System.Tuple<TResult, T>>> generator)
#endif
        {
            if (start == null)
                throw new ArgumentNullException("start", "start is null.");
            if (generator == null)
                throw new ArgumentNullException("generator", "generator is null.");

            return UnfoldHelper(start, generator);
        }

        /// <summary>
        /// Generates a sequence using the specified generator function. Passes the result of the last
        /// application of the generator into the next until None is returned.
        /// </summary>
        /// <typeparam name="T">The type of the resulting sequence.</typeparam>
        /// <param name="start">The value to start with.</param>
        /// <param name="generator">The generator function.</param>
        /// <returns></returns>
        public static IEnumerable<T> Unfold<T>(T start, Func<T, Option<T>> generator)
        {
            if (start == null)
                throw new ArgumentNullException("start", "start is null.");
            if (generator == null)
                throw new ArgumentNullException("generator", "generator is null.");

            return UnfoldHelper(start, generator);
        }

        private static IEnumerable<T> UnfoldHelper<T>(T start, Func<T, Option<T>> generator)
        {
            yield return start;
            var next = start;

            while(true)
            {
                var result = generator(next);

                if(result.IsNone)
                {
                    yield break;
                }

                yield return result.Value;
                next = result.Value;
            }
        }
#if NET_3_5
        private static IEnumerable<TResult> UnfoldHelper<T, TResult>(T start, Func<T, Option<Tuple<TResult, T>>> generator)
#else
        private static IEnumerable<TResult> UnfoldHelper<T, TResult>(T start, Func<T, Option<System.Tuple<TResult, T>>> generator)
#endif
        {
            var next = start;

            while (true)
            {
                var res = generator(next);
                if (res.IsNone)
                    yield break;

                yield return res.Value.Item1;

                next = res.Value.Item2;
            }
        }
    }
}